%{
#include <stdio.h>		/* for NULL */
#include <stdlib.h>		/* for abs */
#include "hoc.h"
#include "math.h"

double last;			/* Last value calculated */

/* Prototype Functions */
int execerror(const char*, const char*);
int yylex();
int yyerror(const char*);

extern int init();
%}
%union {		/* stack type */
	double		val;		/* actual value */
	Symbol_t*	ptr_sym;	/* Symbol Table Pointer */
}
%token	<val>		NUMBER
%token	<ptr_sym>	VAR BLTIN UNDEF
%type	<val>		expr asgn
%right	'='
%left	'+' '-'		/* left associate, same precedence */
%left	'*' '/' '%'	/* left assoc, higher precedence */
%left	UNARY		/* Unary Operators */
%right	'^'			/* Exponentiation */
%%
list:	/* nothing */
	| list endline
	| list asgn endline
	| list expr endline		{ printf("\t%.8g\n", $2); last = $2; }
	| list error endline	{ yyerrok; }
	;

endline: '\n' | ';' ;		/* Line terminator */

asgn:	VAR '=' expr	{ $$ = $1->u.dval = $3; $1->itype = VAR; }
	;

expr: NUMBER
	| VAR {
		if ( $1->itype == UNDEF ) {
			execerror("undefined variable", $1->ptr_cname );
		}
		$$ = $1->u.dval;
	}
	| asgn
	| BLTIN '(' expr ')'	{ $$ = (*($1->u.ptr_dfunc))($3); }
	| expr '+' expr			{ $$ = $1 + $3; }
	| expr '-' expr			{ $$ = $1 - $3; }
	| expr '*' expr 		{ $$ = $1 * $3; }
	| expr '/' expr {
		if ( $3 == 0.0 ) {
			execerror("division by zero", NULL);
		}
		$$ = $1 / $3;
	}
	| expr '%' expr			{ $$ = $1 - (abs((int)($1/$3)) * $3); }
	| expr '^' expr			{ $$ = Pow($1,$3); }
	| '-' expr %prec UNARY	{ $$ = -$2; }
	| '+' expr %prec UNARY	{ $$ = $2; }
	| '(' expr ')'			{ $$ = $2; }
	| '_'					{ $$ = last; }
	;
%%
#include <ctype.h>
#include <signal.h>
#include <setjmp.h>

int warning(const char*, const char*);

#ifdef __cplusplus
extern "C" {
	void fpecatch(int);
	void intcatch(int);
}
#else
	void fpecatch(int);
	void intcatch(int);
#endif

char *progname;
int  lineno = 1;
jmp_buf begin;


int main (int argc, char *argv[]) { /* hoc3 */
	progname = argv[0];			/* Remember the program name */
	init();						/* Initlise Symbol table */

	setjmp(begin);				/* Restart point */

	signal(SIGFPE,fpecatch);	/* Catch Floating Point Exceptions */
	signal(SIGINT,intcatch);	/* Catch Interrupts (Ctrl-C) */

	yyparse();
	return 0;
}

int yylex () { /* hoc3 */
	int cchar;

	/* Eat white space */
	cchar = ' ';
	while ( cchar == ' ' || cchar == '\t' ) {
		cchar = getchar();
	}

	/* End of file */
	if ( cchar == EOF ) {
		return 0;
	}

	/* Comment */
	if ( cchar == '#' ) {
		while ( cchar != '\n' ) {
			cchar = getchar();
		}
	}

	/* Number */
	if ( cchar == '.' || isdigit(cchar) ) {
		ungetc(cchar,stdin);
		scanf("%lf", &yylval.val);
		return NUMBER;
	}

	/* Variable */
	if ( isalpha(cchar) || cchar == '_' ) {
		Symbol_t* ptr_sym;
		char cbuf[100];
		char* ptr_cbuf = cbuf;

		do {
			if ( ptr_cbuf >= cbuf + sizeof(cbuf) - 1 ) {
				ptr_cbuf = '\0';
				execerror("name too long", cbuf);
			}

			*(ptr_cbuf++) = (char) cchar;
			cchar = getchar();
		} while ( cchar != EOF && ( isalnum(cchar) || cchar == '_' ) );

		ungetc(cchar,stdin);
		*ptr_cbuf = '\0';

		ptr_sym = lookup(cbuf);
		if ( ptr_sym == NULL ) {
			ptr_sym = install(cbuf, UNDEF, 0.0);
		}

		yylval.ptr_sym = ptr_sym;
		return ptr_sym->itype == UNDEF ? VAR : ptr_sym->itype;
	}

	/* End of Line */
	if ( cchar == '\n' ) {
		lineno++;
	}

	return cchar;
}

int warning(const char* ptr_cstr, const char* ptr_ctype) {
	int cchar;

	fprintf(stderr, "%s: %s", progname, ptr_cstr);
	if ( ptr_ctype ) {
		fprintf(stderr, " %s", ptr_ctype);
	}
	fprintf(stderr, " near line %d\n", lineno);

	/* Ignore the rest of the line */
	do {
		cchar = getchar();
	} while ( cchar != EOF && cchar != '\n' );

	return 0;
}

/* Report compile-time errors */
int yyerror(const char* ptr_cstr) {
	warning(ptr_cstr, NULL);
	return 0;
}

/* Recover from Run-Time error */
int execerror(const char* ptr_cstr, const char* ptr_ctype) {
	warning(ptr_cstr, ptr_ctype);
	longjmp(begin, 0);
	return 0;
}

/* Catch Exceptions */

#ifdef __cplusplus
extern "C" {
#endif

/* Catch the Floating Point Exception */
void fpecatch(int value) {
	execerror("Floating point excetion", NULL);
}

/* Catch Ctrl-C Interrupts */
void intcatch(int value) {
	execerror("Interrupted", NULL);
}

#ifdef __cplusplus
}
#endif
